import { EventEmitter } from 'events';
import * as MONGO from 'mongodb';
import { TeMap } from './TeTool';

// 拷贝函数 是否包括函数拷贝
function func_copy<T>(obj: T | any, bFunc = false): T {
    var out = {};
    if (obj instanceof Array) {
        out = [];
    }

    if (typeof obj == 'object') {
        for (var key in obj) {
            if (key == 'clone' || key == 'global') {
                continue;
            }
            if (typeof obj[key] == 'function' && !bFunc) {
                continue;
            }
            if (obj[key] == null) {
                out[key] = null;
            }
            else if (typeof obj[key] == 'object') {
                out[key] = func_copy(obj[key], false);
            }
            else {
                out[key] = obj[key];
            }
        }
    }
    else {
        out = obj;
    }
    return <T>out;
}

function func_attach(dst: any, src: any) {
    if (!dst || !src) return;

    for (var key in src) {
        dst[key] = func_copy(src[key]);
    }

    return dst;
}

export class ReHash<T> {
    private fObj: Object = {};
    public args: any;
    private _collection_: MONGO.Collection;

    private _data_: any = {};
    private _online_: boolean = false;

    get findKey() {
        return this.fObj;
    }

    constructor(parent: MONGO.Collection, fObj: Object, args: any) {
        this.fObj = fObj;
        this._data_ = fObj;
        this.args = args;

        this._collection_ = parent;
    }

    force_ok() {
        this._online_ = this._data_._id ? true : false;
    }

    insert(v: T, cb?) {

        // 这里把强制的 findkey 也复制上去
        v = func_attach(v, this.findKey);

        this._collection_.insert(v, (function (_cb, err, res: { ops: T[], result: { n: number, ok: number } }) {
            if (!this._online_) return;
            if (res.result.ok && res.ops.length) {
                this._data_ = res.ops[0];
            }

            _cb && _cb();
        }).bind(this, cb));
    }

    /**
     * 返回的都是 拷贝项，变更都要主动set
     * @param key 
     */
    get(key: string) {
        if (!this._data_) return null;
        return func_copy(this._data_[key]) as any;
    }

    private _changeKey = [];

    set(key: string, value: any) {
        if (!this._online_) return;
        if (this.findKey.hasOwnProperty(key)) return;

        // 如果当前数据是空的，表示之前是没有这个数据的，需要把查询头加上
        if (!this._data_) {
            this._data_ = func_attach({}, this.findKey);
            this._collection_.save(this._data_);
        }

        if (this._data_[key] == value) return;

        if (value == undefined || value == null) {
            delete this._data_[key];
        }
        else {
            this._data_[key] = value;
        }

        this._changeKey.push(key);
        this._check_save();
    }

    save(key: string, value: any) {
        return this.set(key, value);
    }

    msave(a: { k: string, v: any }[]) {
        for (var i = 0; i < a.length; i++)this.set(a[i].k, a[i].v);
    }

    private _auto_save: boolean = false;
    private _check_save() {
        if (!this._data_ || this._auto_save) return;
        setTimeout(this.auto_save.bind(this), 1000);
        this._auto_save = true;
    }

    private auto_save() {
        this._auto_save = false;
        if (!this._data_) return;

        var sets = {};
        var unsets = {};
        for (var i = 0; i < this._changeKey.length; i++) {
            let ckey = this._changeKey[i];
            let cvalue = this._data_[ckey];
            if (cvalue == null || cvalue == undefined) {
                unsets[ckey] = 1;
            }
            else {
                sets[ckey] = this._data_[ckey];
            }
        }

        this._changeKey = [];

        var up_info = { $set: sets };
        if (Object.keys(unsets).length > 0) {
            up_info['$unset'] = unsets;
        }

        this._collection_.updateOne(this.findKey, up_info);
    }

    load(cb?: (s: ReHash<T>) => void) {
        this._online_ = true;
        this._collection_.findOne(this.findKey, this._on_load.bind(this, cb));
        // this._cc_ = this._collection_.find(fobj);
        // this.batchFind();
    }

    get empty() {
        if (this._data_) return false;
        return true;
    }

    get value(): T {
        var ot = func_copy(this._data_) as T;

        for (var key in this.findKey) {
            delete ot[key];
        }

        delete ot['_id'];
        return ot;
    }

    del(key: string | string[]) {
        if (key instanceof Array) {
            for (var i = 0; i < key.length; i++) this.set(key[i], undefined);
        }
        else {
            this.set(key, undefined);
        }
    }

    remove() {
        if (!this._online_) return;
        this._collection_.remove({ _id: this._data_._id });
    }

    private _reload_count = 0;

    private _on_load(cb: (s: ReHash<T>) => void, err, data) {
        if (err) {
            if (this._reload_count > 0) {
                this.load(cb);
                //  LogMgr.log('db load error ', JSON.stringify(err, null, 4));
            }
            this._reload_count++;
        }
        else {
            this._data_ = data;
            if (cb) cb(this);
        }

    }

}

export class ReList<T> {
    private fobj: Object = {};
    private _collection_: MONGO.Collection;

    public args: any;

    private _data_: T[] = [];
    private _online_: boolean = false;

    constructor(parent: MONGO.Collection, fobj: Object, args: any) {
        this.fobj = fobj;
        this._collection_ = parent;
        this.args = args;
    }

    get findKey() {
        return this.fobj;
    }

    get value(): T[] {
        var ot = func_copy(this._data_) as T[];
        for (var i = 0; i < ot.length; i++) {
            delete ot[i]['_id'];
        }

        return ot;
    }

    set(index: number, key: string, value: any) {
        if (!this._online_) return;
        var s_v = this.get_at(index, true);
        if (!s_v) return;

        // 找到里面的

        if (s_v[key] == value) return;

        if (value == undefined) {
            delete s_v[key];
        }
        else {
            s_v[key] = value;
        }

        this._collection_.save(s_v);
    }

    push_back(v: T) {
        v = func_attach(v, this.findKey);

        this._data_.push(v);
        this._collection_.insert(v, (function (idx, err, res: { ops: T[], result: { n: number, ok: number } }) {
            if (!this._online_) return;
            if (res.result.ok && res.ops.length) {
                this._data_[idx - 1] = res.ops[0];
            }
            else {
                this._data_.splice(idx - 1, 1);
            }
        }).bind(this, this._data_.length));
    }

    private _cc_: MONGO.Cursor;

    limit(num: number, cb: any) {
        this._online_ = true;
        this._cc_ = this._collection_.find(this.findKey).limit(num);
        this.batchFind(cb);
    }

    load(cb: any) {
        this._online_ = true;
        this._cc_ = this._collection_.find(this.findKey);
        this.batchFind(cb);
    }

    get length() {
        return this._data_.length;
    }

    get_at(index: number, org: boolean = false) {
        if (index < 0 || index >= this._data_.length) return null;
        if (org) return this._data_[index];
        return func_copy(this._data_[index]) as any;
    }

    rm_at(index: number) {
        if (index < 0 || index >= this._data_.length) return;

        var rp = this._data_.splice(index, 1);
        for (var i = 0; i < rp.length; i++) {
            this._collection_.remove({ _id: rp[i]['_id'] });
        }
    }

    clear_all() {
        while (this.length > 0) {
            this.rm_at(0);
        }
    }

    private _has_same(v: T, fobj: any) {
        var bSame = true;
        for (var key in fobj) {
            if (v[key] != fobj[key]) {
                bSame = false;
                break;
            }
        }

        return bSame;
    }

    del(fObj: any) {
        var idx = -1;
        for (var i = 0; i < this._data_.length; i++) {
            var c = this._data_[i];
            if (this._has_same(c, fObj)) {
                idx = i;
                break;
            }
        }
        // var idx = this._data_.indexOf(v);
        if (idx >= 0) this.rm_at(idx);
    }


    private batchFind(cb: (s: ReList<T>) => void) {
        this._cc_.next((function (_cb, err, result) {
            if (err) {
                this.load(cb);
                //  LogMgr.log('db load error batchFind', JSON.stringify(err, null, 4));
            }
            else {
                if (result == null) {
                    this._on_load(_cb);
                }
                else {
                    // console.log(result)
                    this._data_.push(result);
                    this.batchFind(_cb);
                }
            }
        }).bind(this, cb));
    }

    private _on_load(cb: (s: ReList<T>) => void) {
        // console.log(this._data_);
        if (cb) cb(this);
    }

}

export class TeMongoDBUnit extends EventEmitter {
    private db: MONGO.Db;

    constructor(db: MONGO.Db, collections: string[] = []) {
        super();
        this.db = db;
        this.db.collections(this._check_collections.bind(this, collections));

        this.db.on('error', function (...s) {
            //  LogMgr.log('TeMongoDBUnit', JSON.stringify(s, null, 4));
        })
    }

    private _ct_count = 0;
    private _create_ret(error, succ) {
        this._ct_count--;

        this._init_finish();
    }

    /**
     * 检查数据集是否存在
     */
    private _check_collections(collections: string[], a, b: { s: MONGO.Collection }[]) {
        // console.log(a, b);
        if (a) {

        }
        else {
            var pool: Object = {};
            for (var i = 0; i < b.length; i++) {
                var bc = b[i];
                pool[bc.s['name']] = true;
            }

            // 检查一下要求的是否都在了

            for (var i = 0; i < collections.length; i++) {
                var rc = collections[i];
                if (rc && !pool.hasOwnProperty(rc)) {
                    this._ct_count++;
                    this.db.createCollection(rc, this._create_ret.bind(this));
                }
            }
        }

        this._init_finish();
    }

    private _init_finish() {
        if (this._ct_count != 0) return;

        this.emit('ready');
    }

    public get_unit<T>(dbname: string, fObj: Object = {}, ...args) {
        return new ReHash<T>(this.db.collection(dbname), fObj, args);
    }

    public get_list<T>(dbname: string, fObj: Object = {}, ...args) {
        return new ReList<T>(this.db.collection(dbname), fObj, args);
    }

    public call_func(fun_name: string, ...args) {
        var cb = args.pop();
        var t_args = [];
        if (typeof cb != 'function') {
            args.push(cb);
            cb = null;
        }

        let cmd = `db.eval('${fun_name}(${args.join(',')})')`;
        this.db.command(cmd as any, cb);
    }

    public del(table: string, fobj: Object) {
        this.db.collection(table).deleteMany(fobj, (...args) => {
            // console.log(...args);
        });
    }
}

export interface IFMongoPreInit {
    table: string,
    colltions: string[],
    ready?: boolean
}

export class TeMongoDB extends EventEmitter {

    private _pre_inits: IFMongoPreInit[] = [];

    protected mongodb: MONGO.MongoClient;
    // protected db: MONGO.Db;

    protected db_maps: TeMap<TeMongoDBUnit> = new TeMap<TeMongoDBUnit>();

    /**
     * 
     * @param database_name 数据库的名字
     * @param collections 需要的数据集 可以选，选择后会生成对应的数据集后返回准备状态
     */
    public get_database(database_name: string, collections: string[] = []) {
        if (!this.db_maps.has(database_name)) {
            var opts: MONGO.MongoClientCommonOption = {
            }
            this.db_maps.set(database_name, new TeMongoDBUnit(this.mongodb.db(database_name, opts), collections));
        }

        var map = this.db_maps.get(database_name);
        return map;
    }

    get defaultBDName() {
        if (this.db_maps.keys.length > 0) {
            return this.db_maps.keys[0];
        }
        else {
            return '';
        }
    }

    init(host: string | { host: string, port: number }[], port: number, options: MONGO.MongoClientOptions = {}, pre_inits: IFMongoPreInit[] = []) {
        var uri = '';
        options.autoReconnect = true;

        if (host instanceof Array) {
            let sources = [];
            for (var i = 0; i < host.length; i++) {
                if (options.auth) {
                    sources.push(`${options.auth.user}:${options.auth.password}@${host[i].host}:${host[i].port}`);
                }
                else {
                    sources.push(`${host[i].host}:${host[i].port}`);
                }
            }
            uri = `mongodb://${sources.join(',')}`;
        }
        else {
            if (options.auth) {
                uri = `mongodb://${options.auth.user}:${options.auth.password}@${host}:${port}`;
            }
            else {
                uri = `mongodb://${host}:${port}`;
            }
        }

        if (options.appname) {
            uri += `/${options.appname}`;
            this._pre_inits.push({
                table: options.appname,
                colltions: []
            });
        }

        // options.raw = true;

        this._pre_inits = this._pre_inits.concat(pre_inits);

        this.mongodb = new MONGO.MongoClient(uri, options);

        this.mongodb.connect((a, b) => {
            if (a) {
                console.log(a);
            }
            else {
                b.emit('open22');
            }
        })
        this.mongodb.on('open22', this._on_ready.bind(this));
    }

    private _on_ready() {
        // return;
        for (var i = 0; i < this._pre_inits.length; i++) {
            var cf = this._pre_inits[i];
            var db = this.get_database(cf.table, cf.colltions);
            db.on('ready', (function (idx) {
                this._pre_inits[idx].ready = true;
                this._check_ready();
            }).bind(this, i));
        }

        this._check_ready();
    }

    private _check_ready() {
        var ok = true;
        for (var i = 0; i < this._pre_inits.length; i++) {
            var r = this._pre_inits[i];
            if (!r.ready) {
                ok = false;
                break;
            }
        }

        if (ok) {
            this.emit('ready');
        }
    }
}

export var MongoDBInst = new TeMongoDB();